package com.fyz.aaronfjava.util;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;

/**
 * @Author：AaronF
 * io流工具类
 * 提供了文件读取以及写入的方法
 * 提供获取资源路径下文件的方法
 */
public class IoUtil {

    private static final Logger log = LoggerFactory.getLogger(IoUtil.class);


    /**
     * 默认下载文件的类型
     */
    public static final String DEFAULT_FILE_TYPE = "application/octet-stream";
    /**
     * 空字符
     */
    public static final String BLANK = "";

    /**
     * 写目标文件操作 默认utf-8编码方式
     * @param path 目标文件路径
     * @param content 字符串
     * @param append 是否追加写 true为追加 false为覆盖
     * @return
     */
    public static boolean write(String path, String content, boolean append){
        return write(path,content.getBytes(StandardCharsets.UTF_8),append);
    }

    /**
     * 写目标文件操作
     * @param path 目标文件路径
     * @param data 字节数组
     * @param append 是否追加写 true为追加 false为覆盖
     */
    public static boolean write(String path, byte[] data, boolean append){
        if (data == null || data.length == 0){
            return false;
        }
        File file = new File(path);
        try(OutputStream os=new FileOutputStream(file,append)) {
            //写入
            for (byte datum : data) {
                os.write(datum);
            }
            return true;
        } catch (IOException e) {
            log.info("文件路径{}写入失败",file.getAbsolutePath());
            return false;
        }
    }


    /**
     * 文件拷贝操作
     * @param source 源路径
     * @param target 目标路径
     * @return
     */
    public static boolean copy(String source,String target){
        File sourceFile = new File(source);
        File targetFile = new File(target);
        // 目标路径存在文件或源路径不存在文件则返回
        if (targetFile.exists() || !sourceFile.exists()){
            log.info("目标路径:{}存在文件或源路径:{}不存在文件"
                    ,sourceFile.getAbsolutePath(),targetFile.getAbsolutePath());
            return false;
        }
        try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(sourceFile.toPath()));
             OutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(targetFile.toPath()))) {
            // 获取数据流里有多少个字节可以读取
            byte[] bytes = new byte[inputStream.available()];
            // 将文件读入字节数组中
            inputStream.read(bytes);
            // 将bytes中的内容写入
            outputStream.write(bytes);
            // 刷新输出流，否则不会写出数据
            outputStream.flush();
            log.info("目标路径:{}拷贝至源路径:{}成功"
                    ,sourceFile.getAbsolutePath(),targetFile.getAbsolutePath());
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 读取目标文件 输出为字符串 默认utf-8编码方式
     * @param path 文件路径
     * @return
     * @throws IOException
     */
    public static String readAsString(String path){
        return readAsString(path, StandardCharsets.UTF_8.name());
    }

    /**
     * 读取目标文件 输出为字符串
     *
     * @param path        文件路径
     * @param charsetName 编码
     * @return
     */
    public static String readAsString(String path, String charsetName) {
        File file = new File(path);
        if (!file.exists()){
            log.info("文件路径{}不存在",file.getAbsolutePath());
            return BLANK;
        }
        StringBuilder result = new StringBuilder();
        try (InputStream is = Files.newInputStream(file.toPath());
             InputStreamReader isr = new InputStreamReader(is, charsetName);
             BufferedReader br = new BufferedReader(isr)) {
            String str;
            // 一次读一行,读到尽头为止
            while ((str = br.readLine()) != null) {
                //末尾添加系统分隔符
                result.append(str).append(System.lineSeparator());
            }
            //删除最后一个分隔符
            return result.substring(0, Math.max(result.length() - System.lineSeparator().length(), 0));
        } catch (Exception e) {
            log.info( "{}路径文件读取失败",file.getAbsolutePath());
            return BLANK;
        }
    }

    /**
     * 将服务器的文件返回给输出响应 默认响应类型为二进制文件
     *
     * @param filePath 在服务器的路径
     * @param response 响应对象
     */
    public static boolean download(HttpServletResponse response, String filePath) {
        return download(response, filePath, DEFAULT_FILE_TYPE);
    }

    /**
     * 将服务器的文件返回给输出响应
     *
     * @param filePath 在服务器的路径
     * @param response 响应对象
     * @param fileType 下载文件的类型，在响应头中设置的属性
     */
    public static boolean download(HttpServletResponse response, String filePath, String fileType) {
        File file = new File(filePath);
        //获取文件名
        String fileSimpleName = filePath.substring(filePath.lastIndexOf(File.separatorChar) + 1);
        //如果文件不存在
        if (!file.exists()) {
            return false;
        }
        //该写法自动关闭流，后续不需要手动关闭
        try (InputStream inputStream = new BufferedInputStream(Files.newInputStream(file.toPath()));
             OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {
            //清除空白行
            response.reset();
            // 设置在下载框默认显示的文件名
            response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileSimpleName, String.valueOf(StandardCharsets.UTF_8)));
            //写明文件大小
            response.addHeader("Content-Length", "" + file.length());
            // 指明response的返回文件的类型
            response.setContentType(fileType);
            //得知数据流里有多少个字节可以读取
            byte[] bytes = new byte[inputStream.available()];
            //将文件读入字节数组中
            inputStream.read(bytes);
            //将bytes中的内容写入
            outputStream.write(bytes);
            //刷新输出流，否则不会写出数据
            outputStream.flush();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 文件上传的操作 该方法与spring有一定耦合度(当然如果你可以通过其他方式得到MultipartFile数组也可以)
     * 如果上传的文件过大，可以修改spring配置保证上传成功
     *
     * @param multipartFiles 上传文件对应的数组
     * @param savePath       上传文件所存储的文件夹路径(使用绝对路径，不然很容易出错)
     * @return map集合 键为原文件名 值为新的文件名
     */
    public static Map<String, String> upload(MultipartFile[] multipartFiles, String savePath) {
        Map<String, String> result = new HashMap<>(16);
        //分别接收文件
        for (MultipartFile file : multipartFiles) {
            String newName = upload(file, savePath);
            result.put(file.getOriginalFilename(), newName);
        }
        return result;
    }

    /**
     * 文件上传的操作 该方法与spring有一定耦合度(当然如果你可以通过其他方式得到MultipartFile数组也可以)
     * 如果上传的文件过大，可以修改spring配置保证上传成功
     *
     * @param file     上传文件
     * @param savePath 上传文件所存储的文件夹路径(使用绝对路径，不然很容易出错)
     * @return 新的文件名
     */
    public static String upload(MultipartFile file, String savePath) {
        if (file == null) {
            return BLANK;
        }
        //当文件夹不存在时，创建文件夹
        File folder = new File(savePath);
        //尽量用绝对路径
        if (!folder.exists()) {
            folder.mkdirs();
        }
        if (!folder.isDirectory()) {
            log.info("不是文件夹路径{}",folder.getAbsolutePath());
            return BLANK;
        }
        //重命名上传的文件,为避免重复,使用UUID对文件分别进行命名
        //获取文件名带后缀
        String oldName = file.getOriginalFilename();
        //UUID去掉中间的"-",并将原文件后缀名加入新文件
        String newName = UUID.randomUUID().toString().replace("-", "")
                + oldName.substring(oldName.lastIndexOf("."));
        //文件保存操作
        try {
            //该方法如果判断你的路径是相对路径，则会去其默认的父目录下寻找
            //所以尽量使用绝对路径
            file.transferTo(new File(folder, newName));
            return newName;
        } catch (IOException ex) {
            //操作失败报错并写入返回参数
            ex.printStackTrace();
            return BLANK;
        }
    }

    /**
     * 获取资源路径下的properties文件
     * @param relativePath 相对资源文件夹的路径
     * @return
     */
    public static Properties getPropertiesByRelative(String relativePath){
        Properties properties = new Properties();
        //根据类加载器找到资源路径
        try(InputStream resourceAsStream = IoUtil.class.getClassLoader().getResourceAsStream(relativePath)) {
            //加载资源
            properties.load(resourceAsStream);
        } catch (IOException e) {
            log.info("找不到路径{}对应的资源",relativePath);
        }
        return properties;
    }
}
